mod macros;
use macros::debug::*;
use std::error::Error;
use std::fs::{self, File, OpenOptions};
use std::io::{self, prelude::*, BufRead};
use std::path::Path;
use walkdir::WalkDir;

use colored::*;

#[derive(Debug)]
pub struct Config {
    pub extension: String,
    pub old_string: String,
    pub new_string: Option<String>,
    pub quite: bool,
    pub no_confirm: bool,
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    if !config.quite {
        dbgt!(&config);
    }

    let mut extension = config.extension;
    if extension.starts_with('*') {
        extension = extension[1..].to_string();
    }

    let mut path_list = Vec::new();
    for entry in WalkDir::new(".") {
        let entry = entry.unwrap();
        let path_name = entry.path().display().to_string();
        if !path_name.ends_with(&extension) {
            continue;
        }

        path_list.push(path_name.clone());

        if !config.quite {
            // search and print matches
            if let Ok(lines) = read_lines(&path_name) {
                for line in lines.flatten() {
                    if line.contains(&config.old_string) {
                        print!("{}{}", &path_name.purple(), ":".cyan());
                        for (i, split) in line.split(&config.old_string).enumerate() {
                            if i != 0 {
                                print!(
                                    "{}",
                                    &config.old_string.truecolor(255, 135, 135).bold()
                                );
                            }
                            print!("{}", split);
                        }
                        println!();
                    }
                }
            }
        }
    }

    if let Some(new_string) = config.new_string.as_ref() {
        //dbg!(&contents, &new);

        if !config.no_confirm {
            print!(
                "确定要把 '{}' 替换成 '{}'吗? [y/N]:",
                &config.old_string.truecolor(255, 135, 135).bold(),
                &new_string.green().bold()
            );
            std::io::stdout().flush()?;
            let mut s = String::new();
            std::io::stdin()
                .read_line(&mut s)
                .expect("输入的格式不正确");
            let s = s.trim().to_lowercase();
            if s != "y" && s != "yes" {
                return Ok(());
            }
        }

        for path_name in &path_list {
            if let Ok(contents) = fs::read_to_string(&path_name) {
                // replace-mode
                let new = contents.replace(&config.old_string, new_string);
                if contents != new {
                    let mut file = OpenOptions::new()
                        .write(true)
                        .truncate(true)
                        .open(&path_name)?;
                    file.write_all(new.as_bytes())?;
                    println!("替换了文件: {}", &path_name);
                } else {
                    println!("跳过文件: {}", &path_name);
                }
            }

        }
    }

    Ok(())
}

// The output is wrapped in a Result to allow matching on errors
// Returns an Iterator to the Reader of the lines of the file.
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}
